/*
 * Copyright (c) 2006 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if STDC_HEADERS
# include <stdlib.h>
# include <stdarg.h>
# include <string.h>
# include <float.h>
#endif

#include <stdio.h>

#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_MALLOC_H
# include <malloc.h>
#endif

#if HAVE_LOCALE_H
# include <locale.h>
#endif

#include <getopt.h>
#include <errno.h>
#include <assert.h>

#if HAVE_WINDOWS_H && HAVE_OPENCLIPBOARD
# define WIN32_LEAD_AND_MEAN
# define NOMINMAX
# include <windows.h>
#endif

#if defined(__CYGWIN__)
# include <sys/cygwin.h>
#endif


#include "lib/checkX.h"
#include "lib/util.h"
#include "lib/tokenizer.h"

int opt_nogui = 0;
int opt_notty = 0;
int opt_location = 0;
double opt_timeout = 0.5;
#if defined(DEBUG)
int opt_loglevel = RUN2_LOG_DEBUG;
#elif defined(SILENT)
int opt_loglevel = RUN2_DEFAULT_LOG_SILENT_LEVEL;
#else
int opt_loglevel = RUN2_DEFAULT_LOG_LEVEL;
#endif

typedef struct s_opts_ {
  char* displayname;
  char* appendpath;
  char* prependpath;
  char* replacepath;
  char* xlibname;
} s_opts;

static void cleanup(s_opts* s)
{
  if (!s) return;
  if (s->displayname) { free(s->displayname); s->displayname = NULL; }
  if (s->appendpath)  { free(s->appendpath);  s->appendpath  = NULL; }
  if (s->prependpath) { free(s->prependpath); s->prependpath = NULL; }
  if (s->replacepath) { free(s->replacepath); s->replacepath = NULL; }
  if (s->xlibname)    { free(s->xlibname);    s->xlibname    = NULL; }
}
static void dumpOpts(s_opts* s)
{
  if (s) {
    if (s->displayname) { debugMsg(1, "displayname : '%s'", s->displayname); }
    if (s->appendpath)  { debugMsg(1, "appendpath  : '%s'", s->appendpath);  }
    if (s->prependpath) { debugMsg(1, "prependpath : '%s'", s->prependpath); }
    if (s->replacepath) { debugMsg(1, "replacepath : '%s'", s->replacepath); }
    if (s->xlibname)    { debugMsg(1, "xlibname    : '%s'", s->xlibname);    }
  }
  debugMsg(1, "opt_location: %d", opt_location);
  debugMsg(1, "opt_loglevel: %d", opt_loglevel);
  debugMsg(1, "opt_nogui   : %d", opt_nogui);
  debugMsg(1, "opt_notty   : %d", opt_notty);
  debugMsg(1, "opt_timeout : %5.2f", opt_timeout);
}

static int parse_args(int argc, char* argv[], s_opts* sopt);
#ifndef __CYGWIN__
int WINAPI WinMain (HINSTANCE hSelf, HINSTANCE hPrev, LPSTR cmdline, int nShow);
static int realMain(int argc, char* argv[]);
#endif

#ifdef __CYGWIN__
int main(int argc, char* argv[])
#else /* MinGW */
static int realMain(int argc, char* argv[])
#endif
{
  char* pathToX11Lib = NULL;
  s_opts sopt;
  int save_default_log_level;
  int rc = 0;
  sopt.displayname = NULL;
  sopt.appendpath = NULL;
  sopt.prependpath = NULL;
  sopt.replacepath = NULL;
  sopt.xlibname = NULL;

#ifdef __CYGWIN__
  /* These initializations are performed in WinMain on MinGW */
  opt_nogui = 0;
  opt_notty = 0;
# if defined(DEBUG)
  opt_loglevel = RUN2_LOG_DEBUG;
# elif defined(SILENT)
  opt_loglevel = RUN2_DEFAULT_LOG_SILENT_LEVEL;
# else
  opt_loglevel = RUN2_DEFAULT_LOG_LEVEL;
# endif

  setlocale(LC_ALL, "C");
#endif

  /* allow error messages during option parsing */
  run2_set_program_name(argv[0]);
  save_default_log_level = run2_get_verbose_level();
  run2_set_verbose_level(RUN2_DEFAULT_LOG_LEVEL);
  rc = parse_args(argc, argv, &sopt);
  run2_set_verbose_level (save_default_log_level);
  if (rc != 0)
    {
      rc = (rc < 0 ? -rc : rc);
      goto cleanup_realMain;
    }

  /* propagate options to utils */
  run2_set_tty_mode(!opt_notty);
  run2_set_gui_mode(!opt_nogui);
  run2_set_verbose_level(opt_loglevel);

  dumpOpts(&sopt);

  rc = run2_checkX(sopt.prependpath,
                   sopt.appendpath,
                   sopt.replacepath,
                   sopt.xlibname,
                   sopt.displayname,
                   opt_timeout,
                   &pathToX11Lib);

  if (rc != 0)
  {
    rc = 1;
  }
  else
  {
    if (opt_location && (rc == 0) && pathToX11Lib)
    {
      /* deliberately do NOT use message_() macros */
      fprintf(stdout, "%s", pathToX11Lib);
    }
  }

cleanup_realMain:
  if (pathToX11Lib) free(pathToX11Lib);
  cleanup(&sopt);
#ifdef __CYGWIN__
  infoMsg("Exiting with status %d", rc);
#else /* MinGW */
  debugMsg(1, "returning with status %d", rc);
#endif
  return rc; /* exits with status of 0 if X available */
}

#ifndef __CYGWIN__
int WINAPI
WinMain (HINSTANCE hSelf, HINSTANCE hPrev, LPSTR cmdline, int nShow)
{
  char **argv, **q;
  int argc;
  wchar_t bufTemp[MAX_PATH+2];
  char* bufArg0;
  char* fullCmdLine;
  int rc, i;

  opt_nogui = 0;
  opt_notty = 0;
#if defined(DEBUG)
  opt_loglevel = RUN2_LOG_DEBUG;
#elif defined(SILENT)
  opt_loglevel = RUN2_DEFAULT_LOG_SILENT_LEVEL;
#else
  opt_loglevel = RUN2_DEFAULT_LOG_LEVEL;
#endif

  setlocale(LC_ALL, "C");

  debugMsg(1, "orig cmdline = %s", cmdline);
  rc = GetModuleFileNameW (NULL, bufTemp, sizeof(bufTemp)-1);
  bufTemp[sizeof(bufTemp)-1] = L'\0'; /* 2k/XP don't null-terminate if buffer too small, AND returns incorrect rc */
  bufTemp[rc] = L'\0';
  bufArg0 = (char *) cygwin_create_path (CCP_WIN_W_TO_POSIX, bufTemp);
  if (!bufArg0)
    {
      errorMsg ("Unable to convert filename of this program to posix format: %s",
                strerror (errno));
      return 1;
    }

  fullCmdLine = (char*)malloc(strlen(bufArg0) + strlen(cmdline) + 4);
  if (!fullCmdLine)
    {
      errorMsg("out of memory");
      return 1;
    }

  /* protect embedded spaces in bufArg0 */
  strcpy(fullCmdLine, "\"");
  strcat(fullCmdLine, bufArg0);
  strcat(fullCmdLine, "\" ");
  strcat(fullCmdLine, cmdline);
  free (bufArg0);

  debugMsg(1, "new cmdline = %s", fullCmdLine);

  if ((rc = run2_split_string (fullCmdLine, &argc, (const char ***)&argv)) != 0)
    {
      errorMsg ("Could not create argv vector from |%s|", fullCmdLine);
      goto cleanup;
    }

  debugMsg(1, "argc = %d", argc);
  if(argv) {
    q = argv;
    for (q = argv; *q != NULL; q++)
      debugMsg(1, ": '%s'", *q);
  }

  for (i = 0; i < argc; i++) {
    debugMsg(1, "argv[%d]: '%s'", i, argv[i]);
  }

  rc = realMain(argc, argv);

cleanup:
  run2_freeargv(argv);

  infoMsg("Exiting with status %d", rc);
  // note that this will kill the detached thread if it's still running
  return rc;
}
#endif

static struct option long_options[] =
    {
      {"help", no_argument, NULL, 'h'},
      {"version", no_argument, NULL, 'v'},
      {"display", required_argument, NULL, 'd'},
      {"location", no_argument, NULL, 'l'},
      {"appendpath", required_argument, NULL, 'a'},
      {"prependpath", required_argument, NULL, 'p'},
      {"replacepath", required_argument, NULL, 'r'},
      {"xlibname", required_argument, NULL, 'x'},
      {"timeout", required_argument, NULL, 't'},
#if (ENABLE_GUI == 1)
      {"nogui", no_argument, &opt_nogui, 1},
#endif
#if (ENABLE_TTY == 1)
      {"notty", no_argument, &opt_notty, 1},
#endif
      {"debug", optional_argument, NULL, 'D'},
      {"no-silent", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_LEVEL},
      {"silent", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_SILENT_LEVEL},
      {"no-verbose", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_LEVEL},
      {"verbose", no_argument, &opt_loglevel, RUN2_DEFAULT_LOG_VERBOSE_LEVEL},
      {0, 0, 0, 0}
    };

static char* helpString =
    "Usage: %s [OPTION]...\n"
    "Determines if X is installed, Xserver is running on specified DISPLAY\n"
    "and will accept clients. Returns 0 if yes, nonzero otherwise\n\n"
    "Options:\n"
    "  -h|--help                  print this help message and exit\n"
    "  -v|--version               print version information and exit\n"
    "  -d|--display STRING        use STRING instead of $DISPLAY\n"
    "  -l|--location              print location of Xlib DLL on stdout\n"
    "  -a|--appendpath STRING     append STRING to value of $PATH (cumulative)\n"
    "  -p|--prependpath STRING    prepend STRING to value of $PATH (cumulative)\n"
    "  -r|--replacepath STRING    use STRING instead of $PATH when searching\n"
    "  -x|--xlibname STRING       use exactly STRING instead of fuzzy cygX11-n.dll search\n"
    "  -t|--timeout FLOAT         allow FLOAT seconds to connect with Xserver\n"
    "                             defaults to 0.5, use 0.0 for Xlib's (safe, 12s) timeout\n"
#if (ENABLE_GUI == 1)
    "     --nogui                 disable informational popups\n"
#endif
#if (ENABLE_TTY == 1)
    "     --notty                 disable stderr messages\n"
#endif
    "     --silent                suppress all but fatal error messages"
#ifdef SILENT
" (default)\n"
#else
"\n"
#endif
    "     --no-silent             allow (fatal and non-fatal) error messages"
#ifndef SILENT
" (default)\n"
#else
"\n"
#endif
    "     --no-verbose            same as --no-silent\n"
    "     --verbose               allow error, warning, and info messages\n"
    "     --debug[=N]             turn on debugging messages, as well as\n"
    "                             those enabled by --verbose. N defaults to 1\n"
    "                             --debug=0 is the same as --verbose. Values\n"
    "                             higher than 1 enable increasing details\n"
    "\n"
    "Note that -a defaults to '/usr/bin:/usr/X11R6/bin'.  To eliminate the default,\n"
    "use '-a \"\"'\n";

static int parse_args(int argc, char* argv[], s_opts* sopt)
{
  int i, c, count = 0;
  char* p;
  int save_default_log_level;
  int rc = 0;
  while (1) {
    static char* versionString = PACKAGE_STRING;

    int option_index = 0;
    c = getopt_long(argc, argv, "hvd:la:p:r:x:t:", long_options, &option_index);

    if (c == -1)
      break;

    switch (c) {
      case 0:
        /* If this option set a flag, do nothing else now. */
        if (long_options[option_index].flag != 0)
        break;
        /* else fall thru */
      case 'h':
        realMsg (helpString, run2_get_program_name());
        return 1;
      case 'v':
        realMsg (PACKAGE_STRING, run2_get_program_name());
        return 1;
      case 'l':
        opt_location = 1;
        break;
      case 'd':
        if (sopt->displayname) { free(sopt->displayname); sopt->displayname = NULL; }
        sopt->displayname = run2_strdup(optarg);
        break;
      case 'a':
        if (!sopt->appendpath)
        {
          sopt->appendpath = run2_strdup(optarg);
        }
        else
        {
          p = sopt->appendpath;
          sopt->appendpath = (char*) run2_malloc(strlen(sopt->appendpath) + strlen(optarg) + 2);
          strcpy(sopt->appendpath, p);
          strcat(sopt->appendpath, SEP_CHAR);
          strcat(sopt->appendpath, optarg);
        }
        break;
      case 'p':
        if (!sopt->prependpath)
        {
          sopt->prependpath = run2_strdup(optarg);
        }
        else
        {
          p = sopt->prependpath;
          sopt->prependpath = (char*) run2_malloc(strlen(sopt->prependpath) + strlen(optarg) + 2);
          strcpy(sopt->prependpath, optarg);
          strcat(sopt->prependpath, SEP_CHAR);
          strcat(sopt->prependpath, p);
        }
        break;
      case 'r':
        if (sopt->replacepath) { free(sopt->replacepath); sopt->replacepath = NULL; }
        sopt->replacepath = run2_strdup(optarg);
        break;
      case 'x':
        if (sopt->xlibname) { free(sopt->xlibname); sopt->xlibname = NULL; }
        sopt->xlibname = run2_strdup(optarg);
        break;
      case 't':
        opt_timeout = strtod(optarg, &p);
        if (optarg == p) {
          errorMsg ("bad value specified for timeout: %s\n", optarg);
          return -1;
        }
        break;
      case 'D':
        if (optarg)
          {
            long val = 0;
            if (run2_strtol(optarg, &val) != 0)
              {
                errorMsg("bad value specified for debug: %s\n", optarg);
                return -1;
              }
            if (val <= 0) opt_loglevel = RUN2_DEFAULT_LOG_VERBOSE_LEVEL;
            else opt_loglevel = (val-1) + RUN2_LOG_DEBUG;
          }
        else
          opt_loglevel = RUN2_LOG_DEBUG;
        break;
      case '?':
        /* getopt_long already printed an error message. */
        return -1;
      default:
        errorMsg("unrecognized option");
        return -1;
    }
  }
  /* if everything is turned off, AND we have requested debugging,
     then send it to the tty */
  if ((opt_nogui || (!ENABLE_GUI)) && (opt_notty || (!ENABLE_TTY)))
    if (opt_loglevel >= RUN2_LOG_DEBUG)
      opt_notty = 0;

  return 0;
}

